package com.smsc.headend.connector.netty.decoder.cosem;

import cn.hutool.core.util.StrUtil;
import com.smsc.headend.connector.netty.NettyConfigHelper;
import com.smsc.headend.connector.utils.NetUtil;
import com.smsc.headend.module.protocol.cosem.util.ByteUtils;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.DecoderException;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.List;


@Slf4j
public class CosemByteDecoder extends ByteToMessageDecoder {

    public static final int BYTE_MAX = 2048;

    private CosemByteDecoder.State state;

    private Integer frameLength;

    private ByteBuf tempBuf;

    public CosemByteDecoder() {
        this.state = State.READ_HEADER;
    }

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> out) throws Exception {
        printSz(NetUtil.getIpFromChannel(channelHandlerContext.channel()), byteBuf);
        if (byteBuf.readableBytes() > BYTE_MAX) {
            log.error("received bytes exceed MAX_BYTES 2048");
            byteBuf.skipBytes(byteBuf.readableBytes());
            return;
        }
        while (true) {
            log.trace("state: {}", this.state.toString());
            switch (this.state) {
                case READ_HEADER:
                    if (this.readHeader(byteBuf, out)) {
                        break;
                    }
                    return;
                case READ_HEART_BEAT:
                    if (this.readHeartBeat(byteBuf, out, channelHandlerContext.channel())) {
                        break;
                    }
                    return;
                case READ_WRAPPER:
                    if (this.readCosemWrapper(byteBuf, out)) {
                        break;
                    }
                    return;
                case READ_PDU:
                    if (this.readPduLength(byteBuf, out)) {
                        break;
                    }
                    return;
                case READ_HDLC:
                    if (this.readCosemHDLC(byteBuf, out)) {
                        break;
                    }
                    return;
                case READ_46PDU:
                    if (this.read46PduLength(byteBuf, out)) {
                        break;
                    }
                    return;
                case READ_COM_MODULE:
                    if (this.readComModule(byteBuf, out)) {
                        break;
                    }
                    return;
                default:
                    throw new DecoderException("Unknown state: " + this.state);
            }
        }
    }


    public byte[] printSz(String ipFromChannel, ByteBuf newBuf) {
        ByteBuf copy = newBuf.copy();
        byte[] bytes = new byte[copy.readableBytes()];
        copy.readBytes(bytes);
        log.trace("{}:income bytes:{} ", ipFromChannel == null ? "no ip" : ipFromChannel, ByteUtils.byteToHexString(bytes));
        ReferenceCountUtil.release(copy);
        return bytes;
    }

    private boolean readPduLength(ByteBuf buf, List<Object> out) {
        if (buf.readableBytes() >= this.frameLength + 8) {
            log.trace("read multi packets complete, total length {}", this.frameLength + 8);
            resetToInitStateAndOut(buf.readRetainedSlice(this.frameLength + 8), out);
            this.state = State.READ_HEADER;
            return false;
        }
        log.trace("read length, pdu length {}", buf.readableBytes());
        return false;
    }

    private boolean read46PduLength(ByteBuf buf, List<Object> out) {
        this.state = State.READ_HEADER;
        if (buf.readableBytes() >= this.frameLength + 2) {
            log.trace("read hdlc multi packets complete, total length {}", this.frameLength + 8);
            resetToInitStateAndOut(buf.readRetainedSlice(this.frameLength + 2), out);
            return false;
        }
        log.trace("read hdlc length, pdu length {}", buf.readableBytes());
        return false;
    }

    private boolean readHeader(ByteBuf buf, List<Object> out) {
        if (buf.readableBytes() < 3) {
            return false;
        }

        if (buf.getByte(buf.readerIndex()) == 0x7E) {
            this.state = State.READ_HDLC;
            return true;
        }
        if (buf.getUnsignedShort(buf.readerIndex()) == 0x0D0A) {
            this.state = State.READ_COM_MODULE;
            return true;
        }
        int flag = buf.getUnsignedShort(buf.readerIndex());//COSEM 47  version no; all 47 packet start with 00 01;
        if (flag == 1) {
            this.state = State.READ_WRAPPER;
        } else {
            this.state = State.READ_HEART_BEAT;
        }
        return true;
    }

    private boolean readHeartBeat(ByteBuf buf, List<Object> out, Channel channel) {
        // 判断是不是心跳混合在一起的报文
        String wsDHBKey = null;
        if (null != channel.attr(NettyConfigHelper.wsDHBKey) && StrUtil.isNotEmpty(channel.attr(NettyConfigHelper.wsDHBKey).get())) {
            wsDHBKey = channel.attr(NettyConfigHelper.wsDHBKey).get();
        } else {
            resetToInitStateAndOut(buf.readBytes(buf.readableBytes()), out);
            return false;
        }
        byte[] hbBuf = ByteUtils.hexStringToByte(wsDHBKey);

        if (buf.readableBytes() < hbBuf.length) {
            return false;
        } else if (buf.readableBytes() == hbBuf.length && isHbMatch(buf, hbBuf)) {
            log.trace("read hb complete, total length {}", hbBuf.length);
            resetToInitStateAndOut(buf.readRetainedSlice(hbBuf.length), out);
            return false;
        } else if (buf.readableBytes() > hbBuf.length && isHbMatch(buf, hbBuf)) {
            log.trace("read hb complete, total length {}", hbBuf.length);
            int i = buf.readerIndex();
            resetToInitStateAndOut(buf.readRetainedSlice(hbBuf.length),  out);
            buf.readerIndex(i + hbBuf.length);
            return false;
        } else {
            resetToInitStateAndOut(buf.readBytes(buf.readableBytes()), out);
            return false;
        }
    }

    /**
     * 判断心跳是否匹配
     *
     * @return
     */
    private boolean isHbMatch(ByteBuf buf, byte[] hbBuf) {
        byte[] bytes = new byte[hbBuf.length];
        int readerIndex = buf.readerIndex();

        buf.getBytes(readerIndex, bytes);
        return Arrays.equals(bytes, hbBuf);
    }

    private boolean readCosemWrapper(ByteBuf buf, List<Object> out) {
        if (buf.readableBytes() < 8) {
            return false;
        }
        this.frameLength = buf.getUnsignedShort(buf.readerIndex() + 6);
        log.trace("read wrapper, pdu length {}", this.frameLength);
        if (buf.readableBytes() < this.frameLength + 8) {
            this.state = State.READ_PDU;
            return false;
        }

        if (buf.readableBytes() == this.frameLength + 8) {
            log.trace("read complete, total length {}", this.frameLength + 8);
            resetToInitStateAndOut(buf.readRetainedSlice(this.frameLength + 8), out);
            return false;
        }

        if (buf.readableBytes() > this.frameLength + 8) {
            log.trace("read complete, total length {}", this.frameLength + 8);
            int i = buf.readerIndex();
            resetToInitStateAndOut(buf.readRetainedSlice(this.frameLength + 8), out);
            buf.readerIndex(i + this.frameLength + 8);
            return false;
        }


        return true;
    }

    private boolean readCosemHDLC(ByteBuf buf, List<Object> out) {
        if (buf.readableBytes() < 3) {
            return false;
        }

        // 如 7E A0 22 03 03 xxx  取 A0 22中的后三位即 0 22 为length
        this.frameLength = buf.getUnsignedShort(buf.readerIndex() + 1) & 0x0FFF;
        log.trace("read hdlc, pdu length {}", this.frameLength);
        if (buf.readableBytes() < this.frameLength + 2) {
            this.state = State.READ_46PDU;
            return false;
        }

        if (buf.readableBytes() == this.frameLength + 2) {
            log.trace("read hdlc complete, total length {}", this.frameLength + 2);
            resetToInitStateAndOut(buf.readRetainedSlice(this.frameLength + 2), out);
            return false;
        }

        if (buf.readableBytes() > this.frameLength + 2) {
            log.trace("read hdlc complete, total length {}", this.frameLength + 2);
            int i = buf.readerIndex();
            resetToInitStateAndOut(buf.readRetainedSlice(this.frameLength + 2), out);
            buf.readerIndex(i + this.frameLength + 2);
            return false;
        }

        this.state = State.READ_HEADER;
        return true;
    }

    private boolean readComModule(ByteBuf buf, List<Object> out) {
        int readerIndex = buf.readerIndex();
        for (int i = 2; i < buf.readableBytes() - 1; i++) {
            if (buf.getUnsignedShort(readerIndex + i) == 0x0D0A) {
                // 如果有OD OA回车换行的ascii字符则截断
                resetToInitStateAndOut(buf.readRetainedSlice(i + 2), out);
                return false;
            }
        }
        // 一直没有找到的话也要截断 否则会死循环 有时无OD 0A截断
        resetToInitStateAndOut(buf.readRetainedSlice(buf.readableBytes()), out);
        return false;
    }


    public void resetToInitStateAndOut(ByteBuf buf, List<Object> out) {
        this.state = State.READ_HEADER;
        out.add(buf);
    }

    public enum State {
        READ_HEADER,
        READ_WRAPPER,
        READ_HDLC,
        READ_PDU,
        READ_46PDU,
        READ_HEART_BEAT,
        READ_COM_MODULE;

        State() {
        }
    }
}
